#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
文件工具模块，提供常用的文件操作函数
"""

import os
import shutil
import hashlib
import tempfile
import stat
import time
import platform
from pathlib import Path
from typing import Union, List, Dict, Optional, Tuple, Iterator, Any, Callable
from datetime import datetime

from ..exception.exceptions import FileException
from ..logger.logger import get_logger
from ..exception.decorators import exception_handler

logger = get_logger(__name__)


@exception_handler(exception_type=Exception, reraise_as=FileException)
def get_file_info(file_path: Union[str, Path]) -> Dict[str, Any]:
    """
    获取文件的详细信息
    
    Args:
        file_path: 文件路径
        
    Returns:
        Dict[str, Any]: 包含文件信息的字典，包括大小、创建时间、修改时间、访问时间、
                        是否为目录、是否为文件、文件扩展名等
                        
    Raises:
        FileException: 文件不存在或获取信息失败时抛出
    
    Examples:
        >>> info = get_file_info('example.txt')
        >>> print(f"文件大小: {info['size']} 字节")
        >>> print(f"修改时间: {info['modified_time']}")
    """
    file_path = Path(file_path)
    if not file_path.exists():
        raise FileException(f"文件不存在: {file_path}")
    
    try:
        stat_info = file_path.stat()
        
        # 获取文件权限
        if platform.system() == 'Windows':
            # Windows系统下获取文件权限
            is_readonly = bool(stat_info.st_mode & stat.S_IREAD and not stat_info.st_mode & stat.S_IWRITE)
            permissions = {
                'readonly': is_readonly,
                'hidden': bool(os.stat(file_path).st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN) if hasattr(stat, 'FILE_ATTRIBUTE_HIDDEN') else False,
                'system': bool(os.stat(file_path).st_file_attributes & stat.FILE_ATTRIBUTE_SYSTEM) if hasattr(stat, 'FILE_ATTRIBUTE_SYSTEM') else False,
                'archive': bool(os.stat(file_path).st_file_attributes & stat.FILE_ATTRIBUTE_ARCHIVE) if hasattr(stat, 'FILE_ATTRIBUTE_ARCHIVE') else False,
            }
        else:
            # Unix/Linux系统下获取文件权限
            mode = stat_info.st_mode
            permissions = {
                'owner_read': bool(mode & stat.S_IRUSR),
                'owner_write': bool(mode & stat.S_IWUSR),
                'owner_exec': bool(mode & stat.S_IXUSR),
                'group_read': bool(mode & stat.S_IRGRP),
                'group_write': bool(mode & stat.S_IWGRP),
                'group_exec': bool(mode & stat.S_IXGRP),
                'other_read': bool(mode & stat.S_IROTH),
                'other_write': bool(mode & stat.S_IWOTH),
                'other_exec': bool(mode & stat.S_IXOTH),
            }
        
        # 构建文件信息字典
        file_info = {
            'name': file_path.name,
            'path': str(file_path.absolute()),
            'size': stat_info.st_size,
            'created_time': datetime.fromtimestamp(stat_info.st_ctime),
            'modified_time': datetime.fromtimestamp(stat_info.st_mtime),
            'accessed_time': datetime.fromtimestamp(stat_info.st_atime),
            'is_dir': file_path.is_dir(),
            'is_file': file_path.is_file(),
            'is_symlink': file_path.is_symlink(),
            'extension': file_path.suffix.lower() if file_path.suffix else '',
            'permissions': permissions,
            'owner': get_file_owner(file_path),
        }
        
        return file_info
    except Exception as e:
        raise FileException(f"获取文件信息失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def get_file_owner(file_path: Union[str, Path]) -> str:
    """
    获取文件的所有者
    
    Args:
        file_path: 文件路径
        
    Returns:
        str: 文件所有者名称
        
    Raises:
        FileException: 获取所有者失败时抛出
    """
    file_path = Path(file_path)
    if not file_path.exists():
        raise FileException(f"文件不存在: {file_path}")
    
    try:
        if platform.system() == 'Windows':
            import win32security
            security_descriptor = win32security.GetFileSecurity(
                str(file_path.absolute()), 
                win32security.OWNER_SECURITY_INFORMATION
            )
            owner_sid = security_descriptor.GetSecurityDescriptorOwner()
            name, domain, type = win32security.LookupAccountSid(None, owner_sid)
            return f"{domain}\\{name}"
        else:
            import pwd
            stat_info = file_path.stat()
            return pwd.getpwuid(stat_info.st_uid).pw_name
    except ImportError:
        # 如果缺少必要的模块，返回未知
        return "Unknown"
    except Exception as e:
        raise FileException(f"获取文件所有者失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def copy_file(src_path: Union[str, Path], dst_path: Union[str, Path], overwrite: bool = False) -> None:
    """
    复制文件
    
    Args:
        src_path: 源文件路径
        dst_path: 目标文件路径
        overwrite: 是否覆盖已存在的文件，默认为False
        
    Raises:
        FileException: 源文件不存在、目标文件已存在且不允许覆盖、或复制失败时抛出
    
    Examples:
        >>> copy_file('source.txt', 'destination.txt')
        >>> copy_file('source.txt', 'destination.txt', overwrite=True)  # 覆盖已存在的文件
    """
    src_path = Path(src_path)
    dst_path = Path(dst_path)
    
    if not src_path.exists():
        raise FileException(f"源文件不存在: {src_path}")
    
    if dst_path.exists() and not overwrite:
        raise FileException(f"目标文件已存在且不允许覆盖: {dst_path}")
    
    try:
        # 确保目标目录存在
        dst_path.parent.mkdir(parents=True, exist_ok=True)
        
        # 复制文件
        shutil.copy2(src_path, dst_path)
        logger.info(f"文件复制成功: {src_path} -> {dst_path}")
    except Exception as e:
        raise FileException(f"复制文件失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def move_file(src_path: Union[str, Path], dst_path: Union[str, Path], overwrite: bool = False) -> None:
    """
    移动文件
    
    Args:
        src_path: 源文件路径
        dst_path: 目标文件路径
        overwrite: 是否覆盖已存在的文件，默认为False
        
    Raises:
        FileException: 源文件不存在、目标文件已存在且不允许覆盖、或移动失败时抛出
    
    Examples:
        >>> move_file('source.txt', 'destination.txt')
        >>> move_file('source.txt', 'destination.txt', overwrite=True)  # 覆盖已存在的文件
    """
    src_path = Path(src_path)
    dst_path = Path(dst_path)
    
    if not src_path.exists():
        raise FileException(f"源文件不存在: {src_path}")
    
    if dst_path.exists() and not overwrite:
        raise FileException(f"目标文件已存在且不允许覆盖: {dst_path}")
    
    try:
        # 确保目标目录存在
        dst_path.parent.mkdir(parents=True, exist_ok=True)
        
        # 移动文件
        shutil.move(str(src_path), str(dst_path))
        logger.info(f"文件移动成功: {src_path} -> {dst_path}")
    except Exception as e:
        raise FileException(f"移动文件失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def rename_file(file_path: Union[str, Path], new_name: str) -> Path:
    """
    重命名文件
    
    Args:
        file_path: 文件路径
        new_name: 新文件名（不包含路径）
        
    Returns:
        Path: 重命名后的文件路径
        
    Raises:
        FileException: 文件不存在、新文件已存在、或重命名失败时抛出
    
    Examples:
        >>> new_path = rename_file('old_name.txt', 'new_name.txt')
        >>> print(f"新文件路径: {new_path}")
    """
    file_path = Path(file_path)
    
    if not file_path.exists():
        raise FileException(f"文件不存在: {file_path}")
    
    new_path = file_path.parent / new_name
    
    if new_path.exists():
        raise FileException(f"目标文件已存在: {new_path}")
    
    try:
        file_path.rename(new_path)
        logger.info(f"文件重命名成功: {file_path} -> {new_path}")
        return new_path
    except Exception as e:
        raise FileException(f"重命名文件失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def delete_file(file_path: Union[str, Path], secure: bool = False) -> None:
    """
    删除文件
    
    Args:
        file_path: 文件路径
        secure: 是否安全删除（覆盖文件内容后再删除），默认为False
        
    Raises:
        FileException: 文件不存在或删除失败时抛出
    
    Examples:
        >>> delete_file('file_to_delete.txt')
        >>> delete_file('sensitive_data.txt', secure=True)  # 安全删除
    """
    file_path = Path(file_path)
    
    if not file_path.exists():
        raise FileException(f"文件不存在: {file_path}")
    
    if not file_path.is_file():
        raise FileException(f"指定路径不是文件: {file_path}")
    
    try:
        if secure:
            # 安全删除：先用随机数据覆盖文件内容，再删除
            file_size = file_path.stat().st_size
            with open(file_path, 'wb') as f:
                # 第一遍：用0覆盖
                f.write(b'\x00' * file_size)
                f.flush()
                os.fsync(f.fileno())
                
                # 第二遍：用1覆盖
                f.seek(0)
                f.write(b'\xFF' * file_size)
                f.flush()
                os.fsync(f.fileno())
                
                # 第三遍：用随机数据覆盖
                f.seek(0)
                f.write(os.urandom(file_size))
                f.flush()
                os.fsync(f.fileno())
        
        # 删除文件
        file_path.unlink()
        logger.info(f"文件删除成功: {file_path}")
    except Exception as e:
        raise FileException(f"删除文件失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def calculate_file_hash(file_path: Union[str, Path], algorithm: str = 'md5', chunk_size: int = 8192) -> str:
    """
    计算文件的哈希值
    
    Args:
        file_path: 文件路径
        algorithm: 哈希算法，支持'md5'、'sha1'、'sha256'、'sha512'等，默认为'md5'
        chunk_size: 读取文件的块大小，默认为8KB
        
    Returns:
        str: 文件的哈希值（十六进制字符串）
        
    Raises:
        FileException: 文件不存在、不支持的哈希算法、或计算失败时抛出
    
    Examples:
        >>> md5_hash = calculate_file_hash('example.txt')
        >>> sha256_hash = calculate_file_hash('example.txt', algorithm='sha256')
    """
    file_path = Path(file_path)
    
    if not file_path.exists():
        raise FileException(f"文件不存在: {file_path}")
    
    if not file_path.is_file():
        raise FileException(f"指定路径不是文件: {file_path}")
    
    # 支持的哈希算法
    algorithms = {
        'md5': hashlib.md5,
        'sha1': hashlib.sha1,
        'sha224': hashlib.sha224,
        'sha256': hashlib.sha256,
        'sha384': hashlib.sha384,
        'sha512': hashlib.sha512,
        'blake2b': hashlib.blake2b,
        'blake2s': hashlib.blake2s,
        'sha3_224': hashlib.sha3_224,
        'sha3_256': hashlib.sha3_256,
        'sha3_384': hashlib.sha3_384,
        'sha3_512': hashlib.sha3_512,
    }
    
    if algorithm.lower() not in algorithms:
        raise FileException(f"不支持的哈希算法: {algorithm}")
    
    try:
        hash_obj = algorithms[algorithm.lower()]()
        
        with open(file_path, 'rb') as f:
            for chunk in iter(lambda: f.read(chunk_size), b''):
                hash_obj.update(chunk)
        
        return hash_obj.hexdigest()
    except Exception as e:
        raise FileException(f"计算文件哈希值失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def compare_files(file1_path: Union[str, Path], file2_path: Union[str, Path], 
                  quick_compare: bool = True) -> bool:
    """
    比较两个文件是否相同
    
    Args:
        file1_path: 第一个文件的路径
        file2_path: 第二个文件的路径
        quick_compare: 是否使用快速比较（仅比较文件大小和哈希值），默认为True
                      如果为False，则逐字节比较文件内容
        
    Returns:
        bool: 如果文件相同则返回True，否则返回False
        
    Raises:
        FileException: 文件不存在或比较失败时抛出
    
    Examples:
        >>> is_same = compare_files('file1.txt', 'file2.txt')
        >>> is_same = compare_files('file1.txt', 'file2.txt', quick_compare=False)  # 逐字节比较
    """
    file1_path = Path(file1_path)
    file2_path = Path(file2_path)
    
    if not file1_path.exists():
        raise FileException(f"文件不存在: {file1_path}")
    
    if not file2_path.exists():
        raise FileException(f"文件不存在: {file2_path}")
    
    if not file1_path.is_file() or not file2_path.is_file():
        raise FileException("指定的路径不是文件")
    
    try:
        # 比较文件大小
        if file1_path.stat().st_size != file2_path.stat().st_size:
            return False
        
        if quick_compare:
            # 快速比较：计算并比较文件的哈希值
            hash1 = calculate_file_hash(file1_path)
            hash2 = calculate_file_hash(file2_path)
            return hash1 == hash2
        else:
            # 逐字节比较文件内容
            with open(file1_path, 'rb') as f1, open(file2_path, 'rb') as f2:
                chunk_size = 8192  # 8KB
                while True:
                    chunk1 = f1.read(chunk_size)
                    chunk2 = f2.read(chunk_size)
                    
                    if chunk1 != chunk2:
                        return False
                    
                    if not chunk1:  # 文件读取完毕
                        break
            
            return True
    except Exception as e:
        raise FileException(f"比较文件失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def create_temp_file(content: Union[str, bytes] = None, suffix: str = None, 
                     prefix: str = None, dir: str = None, delete: bool = True) -> Tuple[str, Any]:
    """
    创建临时文件
    
    Args:
        content: 文件内容，可以是字符串或字节，默认为None（空文件）
        suffix: 文件后缀，默认为None
        prefix: 文件前缀，默认为None
        dir: 临时文件所在目录，默认为None（使用系统临时目录）
        delete: 是否在关闭文件时自动删除，默认为True
        
    Returns:
        Tuple[str, Any]: 包含临时文件路径和文件对象的元组
        
    Raises:
        FileException: 创建临时文件失败时抛出
    
    Examples:
        >>> temp_path, temp_file = create_temp_file(content="Hello, World!")
        >>> print(f"临时文件路径: {temp_path}")
        >>> temp_file.close()  # 如果delete=True，文件会在关闭时自动删除
    """
    try:
        # 创建临时文件
        temp_file = tempfile.NamedTemporaryFile(suffix=suffix, prefix=prefix, dir=dir, delete=delete)
        temp_path = temp_file.name
        
        # 写入内容（如果提供）
        if content is not None:
            if isinstance(content, str):
                temp_file.write(content.encode('utf-8'))
            else:
                temp_file.write(content)
            temp_file.flush()
        
        logger.info(f"临时文件创建成功: {temp_path}")
        return temp_path, temp_file
    except Exception as e:
        raise FileException(f"创建临时文件失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def create_temp_dir(suffix: str = None, prefix: str = None, dir: str = None) -> str:
    """
    创建临时目录
    
    Args:
        suffix: 目录后缀，默认为None
        prefix: 目录前缀，默认为None
        dir: 临时目录的父目录，默认为None（使用系统临时目录）
        
    Returns:
        str: 临时目录的路径
        
    Raises:
        FileException: 创建临时目录失败时抛出
    
    Examples:
        >>> temp_dir = create_temp_dir(prefix="my_temp_")
        >>> print(f"临时目录路径: {temp_dir}")
    """
    try:
        temp_dir = tempfile.mkdtemp(suffix=suffix, prefix=prefix, dir=dir)
        logger.info(f"临时目录创建成功: {temp_dir}")
        return temp_dir
    except Exception as e:
        raise FileException(f"创建临时目录失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def is_file_exists(file_path: Union[str, Path]) -> bool:
    """
    检查文件是否存在
    
    Args:
        file_path: 文件路径
        
    Returns:
        bool: 如果文件存在则返回True，否则返回False
    
    Examples:
        >>> if is_file_exists('example.txt'):
        ...     print("文件存在")
        ... else:
        ...     print("文件不存在")
    """
    file_path = Path(file_path)
    return file_path.exists() and file_path.is_file()


@exception_handler(exception_type=Exception, reraise_as=FileException)
def is_dir_exists(dir_path: Union[str, Path]) -> bool:
    """
    检查目录是否存在
    
    Args:
        dir_path: 目录路径
        
    Returns:
        bool: 如果目录存在则返回True，否则返回False
    
    Examples:
        >>> if is_dir_exists('my_directory'):
        ...     print("目录存在")
        ... else:
        ...     print("目录不存在")
    """
    dir_path = Path(dir_path)
    return dir_path.exists() and dir_path.is_dir()


@exception_handler(exception_type=Exception, reraise_as=FileException)
def get_file_size(file_path: Union[str, Path], human_readable: bool = False) -> Union[int, str]:
    """
    获取文件大小
    
    Args:
        file_path: 文件路径
        human_readable: 是否返回人类可读的大小格式（如KB、MB等），默认为False
        
    Returns:
        Union[int, str]: 文件大小（字节数）或人类可读的大小字符串
        
    Raises:
        FileException: 文件不存在或获取大小失败时抛出
    
    Examples:
        >>> size_bytes = get_file_size('example.txt')
        >>> print(f"文件大小: {size_bytes} 字节")
        >>> size_human = get_file_size('example.txt', human_readable=True)
        >>> print(f"文件大小: {size_human}")
    """
    file_path = Path(file_path)
    
    if not file_path.exists():
        raise FileException(f"文件不存在: {file_path}")
    
    if not file_path.is_file():
        raise FileException(f"指定路径不是文件: {file_path}")
    
    try:
        size_bytes = file_path.stat().st_size
        
        if not human_readable:
            return size_bytes
        
        # 转换为人类可读的格式
        for unit in ['B', 'KB', 'MB', 'GB', 'TB', 'PB']:
            if size_bytes < 1024 or unit == 'PB':
                break
            size_bytes /= 1024
        
        return f"{size_bytes:.2f} {unit}"
    except Exception as e:
        raise FileException(f"获取文件大小失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def find_files(directory: Union[str, Path], pattern: str = '*', recursive: bool = True) -> List[Path]:
    """
    在指定目录中查找匹配模式的文件
    
    Args:
        directory: 要搜索的目录
        pattern: 文件匹配模式（支持通配符），默认为'*'（所有文件）
        recursive: 是否递归搜索子目录，默认为True
        
    Returns:
        List[Path]: 匹配的文件路径列表
        
    Raises:
        FileException: 目录不存在或搜索失败时抛出
    
    Examples:
        >>> txt_files = find_files('my_directory', '*.txt')
        >>> for file in txt_files:
        ...     print(file)
    """
    directory = Path(directory)
    
    if not directory.exists():
        raise FileException(f"目录不存在: {directory}")
    
    if not directory.is_dir():
        raise FileException(f"指定路径不是目录: {directory}")
    
    try:
        if recursive:
            return list(directory.glob(f"**/{pattern}"))
        else:
            return list(directory.glob(pattern))
    except Exception as e:
        raise FileException(f"查找文件失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def touch_file(file_path: Union[str, Path], create_dirs: bool = True) -> None:
    """
    创建空文件或更新文件的访问和修改时间
    
    Args:
        file_path: 文件路径
        create_dirs: 是否创建父目录（如果不存在），默认为True
        
    Raises:
        FileException: 创建文件或更新时间失败时抛出
    
    Examples:
        >>> touch_file('new_file.txt')  # 创建新文件
        >>> touch_file('existing_file.txt')  # 更新已存在文件的时间戳
    """
    file_path = Path(file_path)
    
    try:
        if create_dirs:
            file_path.parent.mkdir(parents=True, exist_ok=True)
        
        if file_path.exists():
            # 更新文件的访问和修改时间
            current_time = time.time()
            os.utime(file_path, (current_time, current_time))
            logger.info(f"文件时间戳已更新: {file_path}")
        else:
            # 创建空文件
            file_path.touch()
            logger.info(f"空文件已创建: {file_path}")
    except Exception as e:
        raise FileException(f"创建或更新文件失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def set_file_permissions(file_path: Union[str, Path], mode: int) -> None:
    """
    设置文件权限
    
    Args:
        file_path: 文件路径
        mode: 权限模式（八进制数），例如0o755表示rwxr-xr-x
        
    Raises:
        FileException: 文件不存在或设置权限失败时抛出
    
    Examples:
        >>> set_file_permissions('script.py', 0o755)  # 设置为可执行
        >>> set_file_permissions('data.txt', 0o644)  # 设置为只读
    """
    file_path = Path(file_path)
    
    if not file_path.exists():
        raise FileException(f"文件不存在: {file_path}")
    
    try:
        os.chmod(file_path, mode)
        logger.info(f"文件权限已设置: {file_path} -> {oct(mode)}")
    except Exception as e:
        raise FileException(f"设置文件权限失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def get_file_extension(file_path: Union[str, Path]) -> str:
    """
    获取文件扩展名
    
    Args:
        file_path: 文件路径
        
    Returns:
        str: 文件扩展名（不包含点号），如果没有扩展名则返回空字符串
    
    Examples:
        >>> ext = get_file_extension('document.pdf')
        >>> print(f"文件扩展名: {ext}")  # 输出: 文件扩展名: pdf
    """
    file_path = Path(file_path)
    return file_path.suffix.lstrip('.')


@exception_handler(exception_type=Exception, reraise_as=FileException)
def split_file(file_path: Union[str, Path], chunk_size: int, output_dir: Union[str, Path] = None) -> List[Path]:
    """
    将文件分割成多个小文件
    
    Args:
        file_path: 要分割的文件路径
        chunk_size: 每个分割文件的大小（字节）
        output_dir: 输出目录，默认为None（使用原文件所在目录）
        
    Returns:
        List[Path]: 分割后的文件路径列表
        
    Raises:
        FileException: 文件不存在或分割失败时抛出
    
    Examples:
        >>> chunks = split_file('large_file.dat', 1024*1024)  # 分割为1MB的块
        >>> print(f"分割成 {len(chunks)} 个文件")
    """
    file_path = Path(file_path)
    
    if not file_path.exists():
        raise FileException(f"文件不存在: {file_path}")
    
    if not file_path.is_file():
        raise FileException(f"指定路径不是文件: {file_path}")
    
    if chunk_size <= 0:
        raise FileException("分块大小必须大于0")
    
    # 确定输出目录
    if output_dir is None:
        output_dir = file_path.parent
    else:
        output_dir = Path(output_dir)
        output_dir.mkdir(parents=True, exist_ok=True)
    
    try:
        # 获取文件大小
        file_size = file_path.stat().st_size
        
        # 计算分块数量
        num_chunks = (file_size + chunk_size - 1) // chunk_size  # 向上取整
        
        # 生成分块文件名格式
        base_name = file_path.stem
        extension = file_path.suffix
        chunk_format = f"{base_name}.{{:03d}}{extension}"
        
        chunk_files = []
        with open(file_path, 'rb') as f:
            for i in range(num_chunks):
                chunk_path = output_dir / chunk_format.format(i + 1)
                with open(chunk_path, 'wb') as chunk_f:
                    data = f.read(chunk_size)
                    chunk_f.write(data)
                chunk_files.append(chunk_path)
        
        logger.info(f"文件分割成功: {file_path} -> {num_chunks} 个文件")
        return chunk_files
    except Exception as e:
        raise FileException(f"分割文件失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def merge_files(file_paths: List[Union[str, Path]], output_path: Union[str, Path]) -> None:
    """
    合并多个文件为一个文件
    
    Args:
        file_paths: 要合并的文件路径列表
        output_path: 合并后的输出文件路径
        
    Raises:
        FileException: 文件不存在或合并失败时抛出
    
    Examples:
        >>> merge_files(['part1.dat', 'part2.dat', 'part3.dat'], 'merged.dat')
    """
    if not file_paths:
        raise FileException("文件路径列表不能为空")
    
    # 转换为Path对象
    file_paths = [Path(p) for p in file_paths]
    output_path = Path(output_path)
    
    # 检查所有输入文件是否存在
    for path in file_paths:
        if not path.exists():
            raise FileException(f"文件不存在: {path}")
        if not path.is_file():
            raise FileException(f"指定路径不是文件: {path}")
    
    # 确保输出目录存在
    output_path.parent.mkdir(parents=True, exist_ok=True)
    
    try:
        with open(output_path, 'wb') as out_f:
            for path in file_paths:
                with open(path, 'rb') as in_f:
                    shutil.copyfileobj(in_f, out_f)
        
        logger.info(f"文件合并成功: {len(file_paths)} 个文件 -> {output_path}")
    except Exception as e:
        raise FileException(f"合并文件失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def watch_file(file_path: Union[str, Path], callback: Callable[[Path], None], 
               interval: float = 1.0, timeout: float = None) -> None:
    """
    监视文件变化并调用回调函数
    
    Args:
        file_path: 要监视的文件路径
        callback: 文件变化时调用的回调函数，接受文件路径作为参数
        interval: 检查间隔（秒），默认为1.0
        timeout: 超时时间（秒），默认为None（无限期监视）
        
    Raises:
        FileException: 文件不存在或监视失败时抛出
    
    Examples:
        >>> def on_change(path):
        ...     print(f"文件已更改: {path}")
        >>> watch_file('config.ini', on_change, timeout=60)  # 监视60秒
    """
    file_path = Path(file_path)
    
    if not file_path.exists():
        raise FileException(f"文件不存在: {file_path}")
    
    if not file_path.is_file():
        raise FileException(f"指定路径不是文件: {file_path}")
    
    try:
        # 获取初始修改时间
        last_mtime = file_path.stat().st_mtime
        start_time = time.time()
        
        logger.info(f"开始监视文件: {file_path}")
        
        while True:
            # 检查是否超时
            if timeout is not None and time.time() - start_time > timeout:
                logger.info(f"监视超时: {file_path}")
                break
            
            # 等待指定的间隔时间
            time.sleep(interval)
            
            # 检查文件是否存在
            if not file_path.exists():
                logger.warning(f"监视的文件已被删除: {file_path}")
                break
            
            # 获取当前修改时间
            current_mtime = file_path.stat().st_mtime
            
            # 如果修改时间变化，调用回调函数
            if current_mtime != last_mtime:
                logger.info(f"检测到文件变化: {file_path}")
                callback(file_path)
                last_mtime = current_mtime
    except Exception as e:
        raise FileException(f"监视文件失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def get_mime_type(file_path: Union[str, Path]) -> str:
    """
    获取文件的MIME类型
    
    Args:
        file_path: 文件路径
        
    Returns:
        str: 文件的MIME类型
        
    Raises:
        FileException: 文件不存在或获取MIME类型失败时抛出
    
    Examples:
        >>> mime_type = get_mime_type('document.pdf')
        >>> print(f"MIME类型: {mime_type}")  # 输出: MIME类型: application/pdf
    """
    file_path = Path(file_path)
    
    if not file_path.exists():
        raise FileException(f"文件不存在: {file_path}")
    
    if not file_path.is_file():
        raise FileException(f"指定路径不是文件: {file_path}")
    
    try:
        import mimetypes
        mime_type, _ = mimetypes.guess_type(str(file_path))
        
        if mime_type is None:
            # 如果无法通过扩展名确定MIME类型，尝试通过文件内容判断
            try:
                import magic
                mime_type = magic.from_file(str(file_path), mime=True)
            except ImportError:
                # 如果python-magic库不可用，返回通用类型
                mime_type = 'application/octet-stream'
        
        return mime_type
    except Exception as e:
        raise FileException(f"获取MIME类型失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def compress_file(file_path: Union[str, Path], output_path: Union[str, Path] = None, 
                 format: str = 'zip', compression_level: int = 9) -> Path:
    """
    压缩文件
    
    Args:
        file_path: 要压缩的文件或目录路径
        output_path: 压缩文件输出路径，默认为None（使用原文件路径加上相应的扩展名）
        format: 压缩格式，支持'zip'、'tar'、'gztar'、'bztar'、'xztar'，默认为'zip'
        compression_level: 压缩级别（1-9），默认为9（最高压缩率）
        
    Returns:
        Path: 压缩文件的路径
        
    Raises:
        FileException: 文件不存在、不支持的压缩格式、或压缩失败时抛出
    
    Examples:
        >>> zip_path = compress_file('my_directory', format='zip')
        >>> print(f"压缩文件路径: {zip_path}")
    """
    file_path = Path(file_path)
    
    if not file_path.exists():
        raise FileException(f"文件或目录不存在: {file_path}")
    
    # 支持的压缩格式
    formats = {
        'zip': '.zip',
        'tar': '.tar',
        'gztar': '.tar.gz',
        'bztar': '.tar.bz2',
        'xztar': '.tar.xz'
    }
    
    if format.lower() not in formats:
        raise FileException(f"不支持的压缩格式: {format}")
    
    # 确定输出路径
    if output_path is None:
        output_path = file_path.with_suffix(formats[format.lower()])
    else:
        output_path = Path(output_path)
    
    # 确保输出目录存在
    output_path.parent.mkdir(parents=True, exist_ok=True)
    
    try:
        # 设置压缩级别（仅对zip格式有效）
        import zipfile
        if format.lower() == 'zip':
            compression = zipfile.ZIP_DEFLATED
            if compression_level < 1 or compression_level > 9:
                raise FileException("压缩级别必须在1-9之间")
        
        # 压缩文件或目录
        if file_path.is_dir():
            # 压缩目录
            base_name = str(output_path.with_suffix(''))
            output_path = Path(shutil.make_archive(
                base_name=base_name,
                format=format.lower(),
                root_dir=file_path.parent,
                base_dir=file_path.name
            ))
        else:
            # 压缩单个文件
            if format.lower() == 'zip':
                with zipfile.ZipFile(output_path, 'w', compression=compression) as zipf:
                    zipf.write(
                        file_path, 
                        arcname=file_path.name, 
                        compress_type=compression,
                        compresslevel=compression_level
                    )
            else:
                # 对于其他格式，先创建临时目录，复制文件，然后压缩目录
                temp_dir = create_temp_dir()
                temp_file = Path(temp_dir) / file_path.name
                shutil.copy2(file_path, temp_file)
                
                base_name = str(output_path.with_suffix(''))
                output_path = Path(shutil.make_archive(
                    base_name=base_name,
                    format=format.lower(),
                    root_dir=temp_dir
                ))
                
                # 清理临时目录
                shutil.rmtree(temp_dir)
        
        logger.info(f"文件压缩成功: {file_path} -> {output_path}")
        return output_path
    except Exception as e:
        raise FileException(f"压缩文件失败: {e}")


@exception_handler(exception_type=Exception, reraise_as=FileException)
def extract_archive(archive_path: Union[str, Path], output_dir: Union[str, Path] = None) -> Path:
    """
    解压缩文件
    
    Args:
        archive_path: 压缩文件路径
        output_dir: 解压输出目录，默认为None（使用当前目录）
        
    Returns:
        Path: 解压后的目录路径
        
    Raises:
        FileException: 压缩文件不存在、不支持的压缩格式、或解压失败时抛出
    
    Examples:
        >>> extracted_dir = extract_archive('archive.zip', 'output_folder')
        >>> print(f"解压目录: {extracted_dir}")
    """
    archive_path = Path(archive_path)
    
    if not archive_path.exists():
        raise FileException(f"压缩文件不存在: {archive_path}")
    
    if not archive_path.is_file():
        raise FileException(f"指定路径不是文件: {archive_path}")
    
    # 确定输出目录
    if output_dir is None:
        output_dir = archive_path.parent / archive_path.stem
    else:
        output_dir = Path(output_dir)
    
    # 确保输出目录存在
    output_dir.mkdir(parents=True, exist_ok=True)
    
    try:
        # 根据文件扩展名确定解压方法
        suffix = archive_path.suffix.lower()
        
        if suffix == '.zip':
            import zipfile
            with zipfile.ZipFile(archive_path, 'r') as zipf:
                zipf.extractall(output_dir)
        elif suffix in ['.tar', '.gz', '.bz2', '.xz']:
            import tarfile
            with tarfile.open(archive_path, 'r:*') as tarf:
                tarf.extractall(output_dir)
        elif suffix == '.rar':
            try:
                import rarfile
                with rarfile.RarFile(archive_path, 'r') as rarf:
                    rarf.extractall(output_dir)
            except ImportError:
                raise FileException("解压RAR文件需要安装rarfile库")
        elif suffix == '.7z':
            try:
                import py7zr
                with py7zr.SevenZipFile(archive_path, 'r') as szf:
                    szf.extractall(output_dir)
            except ImportError:
                raise FileException("解压7z文件需要安装py7zr库")
        else:
            # 尝试使用shutil解压
            try:
                shutil.unpack_archive(archive_path, output_dir)
            except ValueError:
                raise FileException(f"不支持的压缩格式: {suffix}")
        
        logger.info(f"文件解压成功: {archive_path} -> {output_dir}")
        return output_dir
    except Exception as e:
        raise FileException(f"解压文件失败: {e}")